package com.bootdo.staticmask.core.mask;

import com.bootdo.common.utils.FileUtil;
import com.bootdo.staticmask.core.bean.ColumnProperty;
import com.bootdo.staticmask.core.bean.OriDataSource;
import com.bootdo.staticmask.core.bean.TableProperty;
import com.bootdo.staticmask.core.oracle.OracleDataSource;
import com.bootdo.staticmask.dao.MaskJobDao;
import com.bootdo.staticmask.domain.MaskFileDO;
import com.bootdo.staticmask.domain.MaskJobDO;
import com.bootdo.staticmask.domain.MaskOracleDO;
import com.bootdo.staticmask.domain.MaskRuleDO;

import com.bootdo.system.controller.LoginController;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.io.*;
import java.nio.charset.Charset;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.sql.ResultSet;
import java.sql.ResultSetMetaData;
import java.text.SimpleDateFormat;
import java.util.*;
import java.util.stream.Stream;
import java.util.zip.ZipEntry;
import java.util.zip.ZipOutputStream;

/**
 * @author czz
 */

public class MaskMain {
	private static Logger logger = LoggerFactory.getLogger(MaskMain.class);
	private static SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyyMMddHHmmss");
	
	private OriDataSource oriDataSource;
	private String resultPath;
	private MaskJobDao maskJobDao;


	public MaskMain(OriDataSource oriDataSource, String resultPath, MaskJobDao maskJobDao) {
		this.oriDataSource = oriDataSource;
		this.resultPath = resultPath;
		this.maskJobDao = maskJobDao;
	}

	public void mask() {
		MaskJobDO maskJobDO = new MaskJobDO();
		maskJobDO.setJobCreatetime(new Date());
		maskJobDO.setJobCreateuser(LoginController.getUSER());
		String jobResultpath;

		List<TableProperty> tablePropertyList = this.oriDataSource.getTablePropertyList();
		Class whichClass = this.oriDataSource.getKey().getClass();
		if (whichClass.equals(MaskFileDO.class)) {
			MaskFileDO maskFile = (MaskFileDO) this.oriDataSource.getKey();
			File file = new File(resultPath + maskFile.getFileName() + "-" + simpleDateFormat.format(new Date()));
			file.mkdirs();

			jobResultpath = file.getAbsolutePath();

//			jobResultpath = resultPath + maskFile.getFileName() + "-" + simpleDateFormat.format(new Date()) + ".zip";
			maskJobDO.setJobName(maskFile.getCategoryName() + "-" + maskFile.getFileName());
			maskJobDO.setDatasourceName(maskFile.getFileName());
			maskJobDO.setJobResultpath(jobResultpath);
			maskJobDao.save(maskJobDO);
			new Thread(()-> textMask(maskFile,tablePropertyList, jobResultpath, maskJobDao, maskJobDO)).start();
		} else if (whichClass.equals(MaskOracleDO.class)) {
			MaskOracleDO maskOracleDO = (MaskOracleDO) oriDataSource.getKey();
			jobResultpath = resultPath + maskOracleDO.getOracleName() + "-" + simpleDateFormat.format(new Date()) + ".zip";

			maskJobDO.setJobName("oracle-" + maskOracleDO.getOracleName());
			maskJobDO.setDatasourceName(maskOracleDO.getOracleName());
			maskJobDO.setJobResultpath(jobResultpath);
			maskJobDao.save(maskJobDO);
			new Thread(()-> oracleMask(maskOracleDO,tablePropertyList, jobResultpath, maskJobDao, maskJobDO)).start();
		}
	}

	private boolean oracleMask(MaskOracleDO maskOracleDO, List<TableProperty> tablePropertyList, String destPath, MaskJobDao maskJobDao, MaskJobDO maskJobDO) {
		long startTime = System.currentTimeMillis();
		logger.info("Start Mask");

		String delimeter = ",";
		String encode = "utf8";
		String lineDelimeter = "\n";

//		MaskOracleDO maskOracleDO = (MaskOracleDO) oriDataSource.getKey();

		try (ZipOutputStream zos = new ZipOutputStream(new BufferedOutputStream(new FileOutputStream(destPath)),
				Charset.forName(encode)); OracleDataSource oracleDataSource = new OracleDataSource(maskOracleDO);) {
			for (TableProperty tableProperty : tablePropertyList) {
				List<ColumnProperty> columnPropertyList = tableProperty.getColumnPropertyList();

				zos.putNextEntry(new ZipEntry(tableProperty.getTableName() + ".csv"));
				ResultSet resultSet = oracleDataSource.getResultSet(tableProperty.getTableName());
				resultSet.setFetchSize(10000);

				int columnCount = columnPropertyList.size();
				String[] row = new String[columnCount];
				ResultSetMetaData metaData = resultSet.getMetaData();
				for (int i = 0; i < columnCount; i++) {
					row[i] = metaData.getColumnName(i + 1);
				}
				zos.write((String.join(delimeter, row) + lineDelimeter).getBytes());

				new ResultSetSpliterator<>(resultSet, r -> getRow(r, columnCount)).stream()
						.parallel().forEach(o -> {
							try {
								rowMask(o, columnCount, columnPropertyList);
								zos.write((String.join(delimeter, o) + lineDelimeter).getBytes(encode));
							} catch (IOException e) {
								throw new RuntimeException(e);
							}
						});
				zos.closeEntry();
			}

		} catch (Exception e) {
			logger.error(e.getMessage(), e);
			File file = new File(destPath);
			if (file.exists() && file.isFile()) {
				file.delete();
			}
			maskJobDO.setJobFinshtime((System.currentTimeMillis()-startTime)/1000);
			maskJobDO.setJobResult("failure");
			maskJobDao.update(maskJobDO);
			return false;
		}
		maskJobDO.setJobFinshtime((System.currentTimeMillis()-startTime)/1000);
		maskJobDO.setJobResult("success");
		maskJobDao.update(maskJobDO);
		return true;
	}
	
	// 从resultset获取一行数据
	private String[] getRow(ResultSet resultSet, int columnCount) {
		String[] temp = new String[columnCount];
		try {
			for (int i = 1; i <= columnCount; i++) {
				temp[i - 1] = resultSet.getString(i);
			}
			return temp;
		} catch (Exception e) {
			throw new RuntimeException(e);
		}
	}
	
	// 对该行数据进行脱敏
	private void rowMask(String[] str, int columnCount, List<ColumnProperty> columnPropertyList) {
		MaskTask task = new MaskTask();
		for (int i = 0; i < columnCount; i++) {
			MaskRuleDO maskRuleDO = columnPropertyList.get(i).getMaskRuleDO();
			if (maskRuleDO != null) {
				task.setData(str[i]);
				task.setRuleName(maskRuleDO.getMaskFunction());
				task.setBeginNum(maskRuleDO.getMaskStart());
				task.setSize(maskRuleDO.getMaskLen());
				str[i] = String.format("\"%s\"", task.maskRuleTask());
			}
		}
	}



	//不压缩txt和csv脱敏
	private void textMask(MaskFileDO maskFile, List<TableProperty> tablePropertyList, String jobResultpath, MaskJobDao maskJobDao, MaskJobDO maskJobDO){
		logger.info("Start Mask");
		long startTime = System.currentTimeMillis();

		String charsetName = maskFile.getFileEncode();
		String delimiter = maskFile.getFileDelimeter();
		String textQualifier = maskFile.getTextQualifier();
		Integer fileFirstline = maskFile.getFileFirstline();
		String recordDelimeter;
		switch (maskFile.getRecordDelimeter()){
			case "CRLF":recordDelimeter = "\r\n";break;
			case "LF":recordDelimeter = "\n";break;
			case "CR": recordDelimeter = "\r";break;
			default: recordDelimeter = "\n";break;
		}

		List<String> readPathList = new ArrayList<>();
		File file = new File(maskFile.getFilePath());
		if (file.isDirectory()){
			for (File f: FileUtil.getFileList(file)){
				for (TableProperty t: tablePropertyList){
					if (f.getName().equals(t.getTableName())){
						readPathList.add(f.getAbsolutePath());
					}
				}
			}
		}else {
			readPathList.add(maskFile.getFilePath());
		}
		logger.info("readList:"+readPathList);
		for (TableProperty tableProperty : tablePropertyList){
			String fileName = "";
			String readPath = "";
			for (String filePath: readPathList) {
				fileName = filePath.substring(filePath.lastIndexOf(File.separator)+1);
				if (fileName.equals(tableProperty.getTableName())){
					readPath = filePath;
					break;
				}
			}
			String writePath = jobResultpath + File.separator + fileName;
			logger.info("read:" + readPath+"\nwrite:"+writePath);
//					outputPath.substring(0,outputPath.lastIndexOf(File.separator)+1) + fileName.substring(0,fileName.lastIndexOf(".")) + "-" + simpleDateFormat.format(new Date()) + ".csv";
			try (BufferedWriter bufferedWriter = new BufferedWriter(new OutputStreamWriter
					(new FileOutputStream(writePath), charsetName), 5 * 1024 * 1024)){
					List<ColumnProperty> columnPropertyList = tableProperty.getColumnPropertyList();
					Stream<String> lines = Files.lines(Paths.get(readPath), Charset.forName(charsetName));
					final int[] firstLine = {0};
					lines.parallel().forEach(line -> {
						if (fileFirstline == 1 && firstLine[0] == 0){
							try {
								bufferedWriter.write(line + recordDelimeter);
								firstLine[0]++;
							} catch (IOException e) {
								e.printStackTrace();
							}
						}else {
							String regex = textQualifier + delimiter + textQualifier;
							String fileDelimiter = maskFile.getFileDelimeter();
							if (textQualifier.length() == 1) {
								line = line.substring(1,line.lastIndexOf(textQualifier)-1);
							}
							String[] content = line.split(regex);
							List<String> replaced = new ArrayList<>();
							MaskMethod maskMethod = new MaskMethod();
							for (int i = 0; i < content.length; i++) {
								MaskRuleDO maskRuleDO = columnPropertyList.get(i).getMaskRuleDO();
								if (maskRuleDO != null) {
									switch (maskRuleDO.getMaskFunction()){
										case "MD5Encrypt":replaced.add(maskMethod.MD5Encrypt(content[i],maskRuleDO.getMaskStart(),maskRuleDO.getMaskLen()));break;
										case "hideMask":replaced.add(maskMethod.hideMask(content[i],maskRuleDO.getMaskStart(),maskRuleDO.getMaskLen()));break;
										case "NULLCutMask":replaced.add(maskMethod.NULLCutMask(content[i],maskRuleDO.getMaskStart(),maskRuleDO.getMaskLen()));break;
										case "replaceMask":replaced.add(maskMethod.replaceMask(content[i],maskRuleDO.getMaskStart(),maskRuleDO.getMaskLen()));break;
										case "shuffleMask":replaced.add(maskMethod.shuffleMask(content[i],maskRuleDO.getMaskStart(),maskRuleDO.getMaskLen()));break;
										case "phoneMask":replaced.add(maskMethod.phoneMask(content[i],maskRuleDO.getMaskStart(),maskRuleDO.getMaskLen()));break;
										case "dateConversion":replaced.add(maskMethod.dateConversion(content[i],maskRuleDO.getMaskStart(),maskRuleDO.getMaskLen()));break;
										case "dateInteger":replaced.add(maskMethod.dateInteger(content[i],maskRuleDO.getMaskStart(),maskRuleDO.getMaskLen()));break;
										case "addressMask":replaced.add(maskMethod.addressMask(content[i],maskRuleDO.getMaskStart(),maskRuleDO.getMaskLen()));break;
										default:break;
									}
								}else {
									replaced.add(content[i]);
								}
							}
							try {
								if (fileDelimiter.equals("\"")||fileDelimiter.equals("'")) {
									bufferedWriter.write(fileDelimiter+String.join(regex, replaced)+fileDelimiter + recordDelimeter);
								}else {
									bufferedWriter.write(String.join(regex, replaced) + recordDelimeter);
								}
							} catch (IOException e) {
								e.printStackTrace();
							}
						}


					});
			}catch (IOException e){
				maskJobDO.setJobFinshtime((System.currentTimeMillis()-startTime)/1000);
				maskJobDO.setJobResult("failure");
				maskJobDao.update(maskJobDO);
				e.printStackTrace();
				return;
			}
		}
		maskJobDO.setJobFinshtime((System.currentTimeMillis()-startTime)/1000);
		maskJobDO.setJobResult("success");
		maskJobDao.update(maskJobDO);
	}

	//	/**
//	 * 脱敏并进行压缩，较大影响速度
//	 * @param maskFile maskFile
//	 * @param tablePropertyList tablePropertyList
//	 * @param jobResultpath destPath
//	 * @param maskJobDao maskJobDao
//	 * @param maskJobDO maskJobDO
//	 */
//	private void textMask2(MaskFileDO maskFile, List<TableProperty> tablePropertyList, String destPath
//			, MaskJobDao maskJobDao, MaskJobDO maskJobDO){
//		long startTime = System.currentTimeMillis();
//		logger.info("Start Mask");
//
//		String path = maskFile.getFilePath();
//		String encode = maskFile.getFileEncode();
//		String delimiter = maskFile.getFileDelimeter();
//		String recordDelimeter;
//		switch (maskFile.getRecordDelimeter()){
//			case "CRLF":recordDelimeter = "\r\n";break;
//			case "LF":recordDelimeter = "\n";break;
//			case "CR": recordDelimeter = "\r";break;
//			default: recordDelimeter = "\n";break;
//		}
//		try (ZipOutputStream zos = new ZipOutputStream(new BufferedOutputStream(new FileOutputStream(destPath))
//				, Charset.forName(encode))) {
//			for (TableProperty tableProperty : tablePropertyList){
//				zos.putNextEntry(new ZipEntry(tableProperty.getTableName()));
//				String filepath = "";
//				File files = new File(path);
//				if (files.isFile()){
//					filepath = path;
//				}else {
//					for (File f: FileUtil.getFileList(files)){
//						if (f.getName().equals(tableProperty.getTableName())){
//							filepath = f.getAbsolutePath();
//							break;
//						}
//					}
//				}
//				List<ColumnProperty> columnPropertyList = tableProperty.getColumnPropertyList();
//				Stream<String> lines = Files.lines(Paths.get(filepath), Charset.forName(encode));
//				lines.forEach(line -> {
//					String[] content = line.split(delimiter);
//					List<String> replaced = new ArrayList<>();
//					MaskMethod maskMethod = new MaskMethod();
//					for (int i = 0; i < content.length; i++) {
//						MaskRuleDO maskRuleDO = columnPropertyList.get(i).getMaskRuleDO();
//						if (maskRuleDO != null) {
//							String fileDelimiter = maskFile.getFileDelimeter();
//							if (fileDelimiter == null||fileDelimiter.length()==0){
//								switch (maskRuleDO.getMaskFunction()){
//									case "MD5Encrypt":replaced.add(maskMethod.MD5Encrypt(content[i],maskRuleDO.getMaskStart(),maskRuleDO.getMaskLen()));break;
//									case "hideMask":replaced.add(maskMethod.hideMask(content[i],maskRuleDO.getMaskStart(),maskRuleDO.getMaskLen()));break;
//									case "NULLCutMask":replaced.add(maskMethod.NULLCutMask(content[i],maskRuleDO.getMaskStart(),maskRuleDO.getMaskLen()));break;
//									case "replaceMask":replaced.add(maskMethod.replaceMask(content[i],maskRuleDO.getMaskStart(),maskRuleDO.getMaskLen()));break;
//									case "shuffleMask":replaced.add(maskMethod.shuffleMask(content[i],maskRuleDO.getMaskStart(),maskRuleDO.getMaskLen()));break;
//									case "phoneMask":replaced.add(maskMethod.phoneMask(content[i],maskRuleDO.getMaskStart(),maskRuleDO.getMaskLen()));break;
//									case "dateConversion":replaced.add(maskMethod.dateConversion(content[i],maskRuleDO.getMaskStart(),maskRuleDO.getMaskLen()));break;
//									case "dateInteger":replaced.add(maskMethod.dateInteger(content[i],maskRuleDO.getMaskStart(),maskRuleDO.getMaskLen()));break;
//									case "addressMask":replaced.add(maskMethod.addressMask(content[i],maskRuleDO.getMaskStart(),maskRuleDO.getMaskLen()));break;
//									default:break;
//								}
//							}else {
//								StringBuilder masked = new StringBuilder();
//								masked.append("\"");
//								switch (maskRuleDO.getMaskFunction()){
//									case "MD5Encrypt":masked.append(maskMethod.MD5Encrypt((content[i].substring(1, content[i].length() - 1)),maskRuleDO.getMaskStart(),maskRuleDO.getMaskLen()));break;
//									case "hideMask":masked.append(maskMethod.hideMask((content[i].substring(1, content[i].length() - 1)),maskRuleDO.getMaskStart(),maskRuleDO.getMaskLen()));break;
//									case "NULLCutMask":masked.append(maskMethod.NULLCutMask((content[i].substring(1, content[i].length() - 1)),maskRuleDO.getMaskStart(),maskRuleDO.getMaskLen()));break;
//									case "replaceMask":masked.append(maskMethod.replaceMask((content[i].substring(1, content[i].length() - 1)),maskRuleDO.getMaskStart(),maskRuleDO.getMaskLen()));break;
//									case "shuffleMask":masked.append(maskMethod.shuffleMask((content[i].substring(1, content[i].length() - 1)),maskRuleDO.getMaskStart(),maskRuleDO.getMaskLen()));break;
//									case "phoneMask":masked.append(maskMethod.phoneMask((content[i].substring(1, content[i].length() - 1)),maskRuleDO.getMaskStart(),maskRuleDO.getMaskLen()));break;
//									case "dateConversion":masked.append(maskMethod.dateConversion((content[i].substring(1, content[i].length() - 1)),maskRuleDO.getMaskStart(),maskRuleDO.getMaskLen()));break;
//									case "dateInteger":masked.append(maskMethod.dateInteger((content[i].substring(1, content[i].length() - 1)),maskRuleDO.getMaskStart(),maskRuleDO.getMaskLen()));break;
//									case "addressMask":masked.append(maskMethod.addressMask((content[i].substring(1, content[i].length() - 1)),maskRuleDO.getMaskStart(),maskRuleDO.getMaskLen()));break;
//									default:break;
//								}
//								masked.append("\"");
//								replaced.add(masked.toString());
//							}
//						} else {
//							replaced.add(content[i]);
//						}
//					}
//					try {
//						zos.write((String.join(delimiter, replaced) + recordDelimeter).getBytes());
//					} catch (IOException e) {
//						e.printStackTrace();
//					}
//				});
//			}
//		} catch (IOException e) {
//			e.printStackTrace();
//			maskJobDO.setJobFinshtime((System.currentTimeMillis()-startTime)/1000);
//			maskJobDO.setJobResult("failure");
//			maskJobDao.update(maskJobDO);
//			return;
//		}
//		logger.info("mask Time:" + (System.currentTimeMillis()-startTime)/1000);
//		maskJobDO.setJobFinshtime((System.currentTimeMillis()-startTime)/1000);
//		maskJobDO.setJobResult("success");
//		maskJobDao.update(maskJobDO);
//	}
}
